package fetzu.app;

/* Copyright 2008 Turadg Aleahmad
 * Derived from CookieConsumer which is
 * Copyright 2007 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import javax.swing.JOptionPane;

import net.oauth.ConsumerProperties;
import net.oauth.OAuth;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthException;
import net.oauth.OAuthMessage;
import net.oauth.OAuthProblemException;
import net.oauth.client.HttpClientPool;
import net.oauth.client.OAuthClient;
import net.oauth.client.OAuthHttpClient;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.RedirectException;

/**
 * Utility methods for consumers that store tokens and secrets in system
 * preferences. Each consumer has a name, and its accessors' credentials are
 * stored in cookies named [name].requestToken, [name].accessToken and
 * [name].tokenSecret.
 * 
 * @author Turadg Aleahmad
 * @author John Kristian
 */
public class PreferencesConsumer {
	private static Logger logger = Logger
			.getLogger("fetzu.app.PreferencesConsumer");

	public static final OAuthClient CLIENT = new OAuthHttpClient(
			new HttpClientPool() {
				// This trivial 'pool' simply allocates a new client every time.
				// More efficient implementations are possible.
				public HttpClient getHttpClient(URL server) {
					return new HttpClient();
				}
			});

	private static Properties consumerProperties = null;

	private static ConsumerProperties consumers = null;

	private OAuthConsumer consumer;
	private Preferences prefs;

	public PreferencesConsumer(String consumerName, Preferences prefs)
			throws IOException {
		consumer = getConsumer(consumerName);
		this.prefs = prefs;
	}

	private static OAuthConsumer getConsumer(String name) throws IOException {
		synchronized (PreferencesConsumer.class) {
			if (consumers == null) {
				String resourceName = "/"
						+ PreferencesConsumer.class.getPackage().getName()
								.replace(".", "/") + "/consumer.properties";
				resourceName = "fetzu/app/consumer.properties";
				consumerProperties = ConsumerProperties
						.getProperties(ConsumerProperties.getResource(
								resourceName, PreferencesConsumer.class
										.getClassLoader()));
				consumers = new ConsumerProperties(consumerProperties);
			}
		}
		OAuthConsumer consumer = consumers.getConsumer(name);
		return consumer;
	}

	public boolean isAuthenticated() throws OAuthException {
		OAuthAccessor accessor = newAccessor();
		return accessor.accessToken != null;
	}

	/**
	 * Construct an accessor from preferences. The resulting accessor won't
	 * necessarily have any tokens.
	 */
	public OAuthAccessor newAccessor() throws OAuthException {
		OAuthAccessor accessor = new OAuthAccessor(consumer);
		String consumerName = (String) consumer.getProperty("name");
		accessor.requestToken = (String) prefs.get(consumerName
				+ ".requestToken", null);
		accessor.accessToken = (String) prefs.get(
				consumerName + ".accessToken", null);
		accessor.tokenSecret = (String) prefs.get(
				consumerName + ".tokenSecret", null);
		return accessor;
	}

	/** Remove all the cookies that contain accessors' data. */
	public void removeAccessors() {
		String[] keys;
		try {
			keys = prefs.keys();
		} catch (BackingStoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new RuntimeException("could not retrieve preferences", e);
		}
		for (String name : keys) {
			if (name.endsWith(".requestToken") || name.endsWith(".accessToken")
					|| name.endsWith(".tokenSecret"))
				prefs.remove(name);
		}
	}

	/**
	 * Get url for user to go to in order to authorize access.
	 * 
	 * @param prefs
	 * @param accessor
	 * @return
	 * @throws OAuthException
	 * @throws IOException
	 * @throws URISyntaxException
	 */
	protected String getAccessAuthorizationUrl(OAuthAccessor accessor)
			throws OAuthException, IOException, URISyntaxException {
		CLIENT.getRequestToken(accessor);
		String consumerName = (String) accessor.consumer.getProperty("name");
		prefs.put(consumerName + ".requestToken", accessor.requestToken);
		prefs.put(consumerName + ".tokenSecret", accessor.tokenSecret);
		String authorizationURL = accessor.consumer.serviceProvider.userAuthorizationURL;
		String parameterizedUrl = OAuth.addParameters(authorizationURL,
				"oauth_token", accessor.requestToken);
		return parameterizedUrl;
	}

	/**
	 * Get a fresh access token from the service provider, after the user has
	 * authorized at getAccessAuthorizationUrl()
	 * 
	 * @throws IOException
	 * @throws URISyntaxException
	 * 
	 * @throws RedirectException
	 *             to obtain authorization
	 */
	void refreshAccessToken(final OAuthAccessor accessor)
			throws OAuthException, IOException, URISyntaxException {
		String consumerName = (String) accessor.consumer.getProperty("name");

		OAuthMessage result = CLIENT.invoke(accessor,
				accessor.consumer.serviceProvider.accessTokenURL, OAuth
						.newList("oauth_token", accessor.requestToken));
		Map<String, String> responseParameters = OAuth.newMap(result
				.getParameters());
		accessor.accessToken = responseParameters.get("oauth_token");
		accessor.tokenSecret = responseParameters.get("oauth_token_secret");
		if (accessor.accessToken != null) {
			prefs.remove(consumerName + ".requestToken");
			prefs.put(consumerName + ".accessToken", accessor.accessToken);
			prefs.put(consumerName + ".tokenSecret", accessor.tokenSecret);
		} else {
			OAuthProblemException problem = new OAuthProblemException(
					"parameter_absent");
			problem.setParameter("oauth_parameters_absent", "oauth_token");
			problem.getParameters().putAll(result.getDump());
			throw problem;
		}
		logger.fine("we've got the accessToken and tokenSecret now");
	}

	/**
	 * The names of problems from which a consumer can recover by getting a
	 * fresh token.
	 */
	private static final Collection<String> RECOVERABLE_PROBLEMS = new HashSet<String>();
	static {
		RECOVERABLE_PROBLEMS.add("token_revoked");
		RECOVERABLE_PROBLEMS.add("token_expired");
		RECOVERABLE_PROBLEMS.add("permission_unknown");
		// In the case of permission_unknown, getting a fresh token
		// will cause the Service Provider to ask the User to decide.
	}

}
